1 Part I: Introduction to ggplot2

1.2 Preparation

You will find the material for this lecture at https://github.com/cbhurley/CRT2021vis

First install these packages

install.packages(c("tidyverse", "dataReporter", "timetk","palmerpenguins"))

and load with

library(tidyverse)
library(timetk) # for the data
library(palmerpenguins) # more data

1.3 Why visualise data?

We can summarise data with numeric summaries like mean, standard deviation and correlation. Why do we need more?

##        x1             x2             x3             x4    
##  Min.   : 4.0   Min.   : 4.0   Min.   : 4.0   Min.   : 8  
##  1st Qu.: 6.5   1st Qu.: 6.5   1st Qu.: 6.5   1st Qu.: 8  
##  Median : 9.0   Median : 9.0   Median : 9.0   Median : 8  
##  Mean   : 9.0   Mean   : 9.0   Mean   : 9.0   Mean   : 9  
##  3rd Qu.:11.5   3rd Qu.:11.5   3rd Qu.:11.5   3rd Qu.: 8  
##  Max.   :14.0   Max.   :14.0   Max.   :14.0   Max.   :19
##        y1               y2              y3              y4        
##  Min.   : 4.260   Min.   :3.100   Min.   : 5.39   Min.   : 5.250  
##  1st Qu.: 6.315   1st Qu.:6.695   1st Qu.: 6.25   1st Qu.: 6.170  
##  Median : 7.580   Median :8.140   Median : 7.11   Median : 7.040  
##  Mean   : 7.501   Mean   :7.501   Mean   : 7.50   Mean   : 7.501  
##  3rd Qu.: 8.570   3rd Qu.:8.950   3rd Qu.: 7.98   3rd Qu.: 8.190  
##  Max.   :10.840   Max.   :9.260   Max.   :12.74   Max.   :12.500

Along the same lines is the Datasaurus dataset of Alberto Cairo, who constructs 12 pairwise plots sharing summary stats with a point plot of a dinosaur. See https://www.autodesk.com/research/publications/same-stats-different-graphs and https://cran.r-project.org/web/packages/datasauRus/vignettes/Datasaurus.html

1.4 The data

We will use a bike sharing dataset, from the UCI machine learning archive https://archive.ics.uci.edu/ml/machine-learning-databases/00275/

There is daily data and hourly, we will use the hourly.

The data is available in the R package timetk.

glimpse(bike_sharing_daily)
## Rows: 731
## Columns: 16
## $ instant    <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, …
## $ dteday     <date> 2011-01-01, 2011-01-02, 2011-01-03, 2011-01-04, 2011-01-05…
## $ season     <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,…
## $ yr         <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
## $ mnth       <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,…
## $ holiday    <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,…
## $ weekday    <dbl> 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4,…
## $ workingday <dbl> 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1,…
## $ weathersit <dbl> 2, 2, 1, 1, 1, 1, 2, 2, 1, 1, 2, 1, 1, 1, 2, 1, 2, 2, 2, 2,…
## $ temp       <dbl> 0.3441670, 0.3634780, 0.1963640, 0.2000000, 0.2269570, 0.20…
## $ atemp      <dbl> 0.3636250, 0.3537390, 0.1894050, 0.2121220, 0.2292700, 0.23…
## $ hum        <dbl> 0.805833, 0.696087, 0.437273, 0.590435, 0.436957, 0.518261,…
## $ windspeed  <dbl> 0.1604460, 0.2485390, 0.2483090, 0.1602960, 0.1869000, 0.08…
## $ casual     <dbl> 331, 131, 120, 108, 82, 88, 148, 68, 54, 41, 43, 25, 38, 54…
## $ registered <dbl> 654, 670, 1229, 1454, 1518, 1518, 1362, 891, 768, 1280, 122…
## $ cnt        <dbl> 985, 801, 1349, 1562, 1600, 1606, 1510, 959, 822, 1321, 126…

This dataset contains the daily count of rental bike transactions between years 2011 and 2012 in Capital bikeshare system with the corresponding weather and seasonal information.

The two temperature variables temp and atemp (feeling temperature), both are normalised.

Some of these variables should be factors, so we will fix this up first.

bike <- bike_sharing_daily
bike$season <- c("Winter","Spring", "Summer","Fall")[bike$season]
bike$season <- factor(bike$season, levels = c("Winter","Spring", "Summer","Fall"))  

bike$mnth <- factor(bike$mnth)

bike$holiday <- factor(c("No","Yes")[bike$holiday+1])
bike$workingday <- factor(c("No","Yes")[bike$workingday+1])

bike$yr <- factor(c("2011","2012")[bike$yr+1])

bike$weathersit <- c("clear", "cloudy", "lightP", "heavyP")[bike$weathersit]
bike$weathersit <- factor(bike$weathersit, levels= c("clear", "cloudy", "lightP", "heavyP"))

glimpse(bike)
## Rows: 731
## Columns: 16
## $ instant    <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, …
## $ dteday     <date> 2011-01-01, 2011-01-02, 2011-01-03, 2011-01-04, 2011-01-05…
## $ season     <fct> Winter, Winter, Winter, Winter, Winter, Winter, Winter, Win…
## $ yr         <fct> 2011, 2011, 2011, 2011, 2011, 2011, 2011, 2011, 2011, 2011,…
## $ mnth       <fct> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,…
## $ holiday    <fct> No, No, No, No, No, No, No, No, No, No, No, No, No, No, No,…
## $ weekday    <dbl> 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4,…
## $ workingday <fct> No, No, Yes, Yes, Yes, Yes, Yes, No, No, Yes, Yes, Yes, Yes…
## $ weathersit <fct> cloudy, cloudy, clear, clear, clear, clear, cloudy, cloudy,…
## $ temp       <dbl> 0.3441670, 0.3634780, 0.1963640, 0.2000000, 0.2269570, 0.20…
## $ atemp      <dbl> 0.3636250, 0.3537390, 0.1894050, 0.2121220, 0.2292700, 0.23…
## $ hum        <dbl> 0.805833, 0.696087, 0.437273, 0.590435, 0.436957, 0.518261,…
## $ windspeed  <dbl> 0.1604460, 0.2485390, 0.2483090, 0.1602960, 0.1869000, 0.08…
## $ casual     <dbl> 331, 131, 120, 108, 82, 88, 148, 68, 54, 41, 43, 25, 38, 54…
## $ registered <dbl> 654, 670, 1229, 1454, 1518, 1518, 1362, 891, 768, 1280, 122…
## $ cnt        <dbl> 985, 801, 1349, 1562, 1600, 1606, 1510, 959, 822, 1321, 126…

A good way to check the data for oddities is to use a package that will do a plot, and summary of all the variables. You could do this manually, one variable at a time, but this saves work:

dataReporter::makeDataReport(bike,replace=TRUE)

You will find the results at dataReporter_bike.pdf.

One thing to note from the results, is that `weathersit’ has a zero count for the fourth level. We will drop this.

table(bike$weathersit)
## 
##  clear cloudy lightP heavyP 
##    463    247     21      0
bike <- droplevels(bike)
table(bike$weathersit)
## 
##  clear cloudy lightP 
##    463    247     21

1.5 ggplot2

In R, you can use base R (barplot, hist, plot). Here we will use ggplot2 functions.

ggplot2 is an R package authored by Hadley Wickham for elegant graphics in R. Click on this link for lots of information.

Also check out he primer at

You will find more references at the end of this document.

1.6 Basic plots

Histogram

Our report used one plot per variable, barplots for factors and histograms for numeric variables.

ggplot(data=bike, 
       mapping= aes(x=cnt)) + geom_histogram()

ggplot(data=bike, mapping= aes(x=cnt)) + 
  geom_histogram(fill="lightblue", color="navy")

  • Here mapping describes how the data is mapped to aesthetics.

  • Aesthetics are what you see on the plot. Here that cnt goes on the x axis.

  • The geom is what is drawn, here a histogram.

  • In the second variant, the fill colour and outline colour for the bars is specified.

  • You could use another variable for the fill or color:

ggplot(data=bike, mapping= aes(x=cnt, fill=yr)) + 
  geom_histogram(color="black")

  • You are seeing here for each bar, the count for 2011 and 2012.

Barplot

For the barplot, the x variable is a factor, or something that can be interpreted as a factor.

ggplot(data=bike, mapping= aes(x=weathersit)) + 
  geom_bar(fill="lightblue", color="navy")

You can add color to this

ggplot(data=bike, mapping= aes(x=weathersit, fill=yr)) + 
  geom_bar( )

and switch to proportions with

ggplot(data=bike, mapping= aes(x=weathersit, fill=yr)) + 
  geom_bar( position="fill")

geom_col is like geom_bar, except you have a y variable. The plot below shows the number of bikes rented for each month, and in the second variant we colour by year.

ggplot(data=bike, mapping= aes(x=mnth, y=cnt)) + 
  geom_col(color="lightblue", fill="lightblue")

ggplot(data=bike, mapping= aes(x=mnth, y=cnt, fill=yr)) + 
  geom_col()

A scatterplot

The scatterplot is for two numeric variables, and you use geom_point

ggplot(data=bike, aes(x=temp, y=cnt)) + geom_point()

We can easily bring a third variable for the point colour, and a fourth for shape:

ggplot(data=bike, aes(x=temp, y=cnt, color=yr)) + 
  geom_point()

ggplot(data=bike, aes(x=temp, y=cnt, color=yr, shape=season)) + 
  geom_point()

The plot with shape and colour works better when there is more separation between the froups.

In the plot below, weekday is a numeric variable so it is mapped to shades of blue. This variable is better represented as a factor, which changes the ggplot.

ggplot(data=bike, aes(x=temp, y=cnt, color=weekday)) + 
  geom_point()

bike$weekday <- factor(bike$weekday)

ggplot(data=bike, aes(x=temp, y=cnt, color=weekday)) + 
  geom_point()

Would we expect the relationship between cnt and temp to change with weekday?

1.6.1 A time plot

ggplot(data=bike, aes(x=dteday, y=cnt)) + 
  geom_line()

We could use geom_point here but geom_line connects the dots.

Note that dteday is a date variable in the dataset, which is interpreted correctly for the plot.

library(lubridate)
ggplot(data=bike, aes(x=yday(dteday), y=cnt, color=yr)) + 
  geom_line()

1.7 Practice 1

Using the penguins dataset, make these two plots

1.8 Anatomy of a ggplot

We have seen that aesthetics are linked to variables in a dataset. Aesthetics we have considered are x, y, fill and color.

Here are some other aesthetics (figure from Wilke book)

  • shape and line type aesthetics are for categorical data (ie factors)
  • others can represent factors or numeric variables

scales map data values into aesthetics. Eg use scale_x_log10() to log the x coordinates. Default is scale_x_continuous. Adjust this to change axes, for example

ggplot(data=bike, aes(x=yday(dteday), y=cnt, color=yr)) + 
  geom_line()+
  scale_x_continuous(breaks=cumsum(c(1,31,28,31,30,31,30,31,31,30,31,30)),
                     labels=month.abb[1:12])+
  xlab("Date")

layers components of the plot are added in layers, using +

legend refers to the color index on the right. You will get legends for fill, linetype and other aesthetics.

themes refer to non-data components, the background, title, font and so on. Work on this to pretty-up the plot.

facets are important, this is a way to split up a plot by a factor. It increases the number of dimensions that can be plotted.

ggplot(data=bike, aes(x=temp, y=cnt, color=yr)) + 
  geom_point()+
  facet_wrap(~ season)

1.9 More on layers

In ggplot the idea is that you build up to the desired plot bit by bit.

Going back to the first scatterplot, we can add a smooth, and or a regression line:

ggplot(data=bike, aes(x=temp, y=cnt)) + 
  geom_point()+
  geom_smooth()

p <- ggplot(data=bike, aes(x=temp, y=cnt)) + 
     geom_point()

p + geom_smooth(method="lm", se=FALSE)

By default geom_smooth uses loess, for 1000 observations or less, and a gam otherwise.

The interesting thing is if you have colors or groups, the geom_smooth will respect that:

ggplot(data=bike, aes(x=temp, y=cnt, color=yr)) + 
  geom_point()+
  geom_smooth(se=FALSE)

1.10 More on facets

More than one variable can be added tp the facet wrap. For example, you could use facet_wrap(~ season+ yr), or even better facet_grid

ggplot(data=bike, aes(x=temp, y=cnt, color=yr)) + 
  geom_point()+
  facet_grid( workingday ~ season)

And if we add smooths or other fits, these will respect the groups for the colors and facets.

ggplot(data=bike, aes(x=temp, y=cnt, color=yr)) + 
  geom_point()+
  facet_grid( workingday ~ season)+
  geom_smooth(se=F)

Suppose you want to fit regression lines instead, then simply use method=lm in the geom_smooth.

This will be essentially fitting the model to the interaction of season and yr with temp.

To compare the fit with and without interaction, we create the fit and added the fitted values to the dataset, and then use geom_line.

fit <- lm(cnt ~ temp+season+yr, data=bike)
bike %>% 
  mutate(fit = fitted(fit)) %>%
  ggplot(aes(x=temp, y=cnt, color=yr)) + 
  geom_point(size=1)+  # points smaller
  facet_grid( workingday ~ season)+
  geom_smooth(method=lm, se=F)+
  geom_line(aes(y=fit), linetype="twodash", size=1.5)

The fit without interaction does poorly for summer.

1.11 Practice 2

Using the penguins dataset, make these two plots

Hint: there are a few ways to get rid of the NA in the sex legend, one is to use scale_colour_discrete(na.translate = FALSE)

After that you can check out many more examples with this data at https://allisonhorst.github.io/palmerpenguins/articles/intro.html

1.12 Alternatives to histogram

Density plot

We can fit a density estimate to one numeric variable, just replace geom_histogram with geom_density

Here we compare the histogram and density

ggplot(data=bike, aes(x=cnt)) + 
  geom_density(color="red")+
  geom_histogram(aes(y=after_stat(density)),fill="lightblue", color="navy", alpha=.5)

If you use a fill aesthetic for the density you get

ggplot(data=bike, aes(x=cnt, fill=yr)) + 
  geom_density(alpha=.5)

alpha gives some transparency.

Boxplots

A boxplot in ggplot needs a y and x variable.

ggplot(data=bike, aes(y=cnt,x=season)) + 
  geom_boxplot(varwidth=TRUE) # width of box proportional to sqrt of nobs

If you do not need an x variable use:

ggplot(data=bike, aes(y=cnt), x=0) + # any number will do
  geom_boxplot()  

Violin plots are a variant using a density folded-over

ggplot(data=bike, aes(y=cnt,x=season)) + 
  geom_violin(fill="lightblue")

1.13 References

Data visualization a practical introduction by Kieran Healy https://socviz.co

Fundamentals of Data Visualization by Claus Wilke https://clauswilke.com/dataviz/ (no R)

LS0tCnRpdGxlOiAiRGF0YSB2aXN1YWxpc2F0aW9uIGluIFIiCmF1dGhvcjogIkNhdGhlcmluZSBIdXJsZXkiCmRhdGU6ICI3LzkvMjAyMSIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0aGVtZTogcmVhZGFibGUKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIHRvYzogdHJ1ZQogICAgdG9jX2RlcHRoOiAyCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKZWRpdG9yX29wdGlvbnM6CiAgY2h1bmtfb3V0cHV0X3R5cGU6IGNvbnNvbGUKLS0tCgo8IS0tIDxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+IC0tPgo8IS0tICAgYm9keSwgdGQgeyAtLT4KPCEtLSAgICAgZm9udC1zaXplOiAxNHB0OyAtLT4KPCEtLSAgIH0gLS0+CjwhLS0gY29kZS5yeyAtLT4KPCEtLSAgIGZvbnQtc2l6ZTogMTJwdDsgLS0+CjwhLS0gfSAtLT4KPCEtLSBwcmUgeyAtLT4KPCEtLSAgIGZvbnQtc2l6ZTogMTJwdCAtLT4KPCEtLSB9IC0tPgo8IS0tIDwvc3R5bGU+IC0tPgoKCgoKCgoKCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUYpCmBgYAojIFBhcnQgSTogSW50cm9kdWN0aW9uIHRvIGdncGxvdDIKCgojIyBSZWNvbW1lbmRlZCBib29rCgoKYGBge3IsIGVjaG89RiwgZXZhbD1ULCBvdXQud2lkdGg9IjI1JSIsIGZpZy5hbGlnbj0nY2VudGVyJ30Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImZpZ3Mvd2FpbmVyLnBuZyIpCmBgYAoKCiMjIFByZXBhcmF0aW9uCgpZb3Ugd2lsbCBmaW5kIHRoZSBtYXRlcmlhbCBmb3IgdGhpcyBsZWN0dXJlIGF0IDxodHRwczovL2dpdGh1Yi5jb20vY2JodXJsZXkvQ1JUMjAyMXZpcz4KCkZpcnN0IGluc3RhbGwgdGhlc2UgcGFja2FnZXMKCmBgYHtyLCBldmFsPUZ9Cmluc3RhbGwucGFja2FnZXMoYygidGlkeXZlcnNlIiwgImRhdGFSZXBvcnRlciIsICJ0aW1ldGsiLCJwYWxtZXJwZW5ndWlucyIpKQpgYGAKCmFuZCBsb2FkIHdpdGgKCmBgYHtyfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeSh0aW1ldGspICMgZm9yIHRoZSBkYXRhCmxpYnJhcnkocGFsbWVycGVuZ3VpbnMpICMgbW9yZSBkYXRhCmBgYAoKCgojIyBXaHkgdmlzdWFsaXNlIGRhdGE/CgpXZSBjYW4gc3VtbWFyaXNlIGRhdGEgd2l0aCBudW1lcmljIHN1bW1hcmllcyBsaWtlIG1lYW4sIHN0YW5kYXJkIGRldmlhdGlvbiBhbmQgY29ycmVsYXRpb24uCldoeSBkbyB3ZSBuZWVkIG1vcmU/CgpgYGB7ciwgYW5zY29tYmUsIGVjaG89RixwdXJsPUZBTFNFfQpzdW1tYXJ5KGFuc2NvbWJlWywxOjRdKQpzdW1tYXJ5KGFuc2NvbWJlWyw1OjhdKQojIy0tIG5vdyBzb21lICJtYWdpYyIgdG8gZG8gdGhlIDQgcmVncmVzc2lvbnMgaW4gYSBsb29wOgpmZiA8LSB5IH4geAptb2RzIDwtIHNldE5hbWVzKGFzLmxpc3QoMTo0KSwgcGFzdGUwKCJsbSIsIDE6NCkpCgpmb3IoaSBpbiAxOjQpIHsKICBmZlsyOjNdIDwtIGxhcHBseShwYXN0ZTAoYygieSIsIngiKSwgaSksIGFzLm5hbWUpCiAgIyMgb3IgICBmZltbMl1dIDwtIGFzLm5hbWUocGFzdGUwKCJ5IiwgaSkpCiAgIyMgICAgICBmZltbM11dIDwtIGFzLm5hbWUocGFzdGUwKCJ4IiwgaSkpCiAgbW9kc1tbaV1dIDwtIGxtaSA8LSBsbShmZiwgZGF0YSA9IGFuc2NvbWJlKQogICMgcHJpbnQoYW5vdmEobG1pKSkKfQoKIyMgU2VlIGhvdyBjbG9zZSB0aGV5IGFyZSAobnVtZXJpY2FsbHkhKQojIHNhcHBseShtb2RzLCBjb2VmKQojIGxhcHBseShtb2RzLCBmdW5jdGlvbihmbSkgY29lZihzdW1tYXJ5KGZtKSkpCgojIyBOb3csIGRvIHdoYXQgeW91IHNob3VsZCBoYXZlIGRvbmUgaW4gdGhlIGZpcnN0IHBsYWNlOiBQTE9UUwpvcCA8LSBwYXIobWZyb3cgPSBjKDIsIDIpLCBtYXIgPSAwLjErYyg0LDQsMSwxKSwgb21hID0gIGMoMCwgMCwgMiwgMCkpCmZvcihpIGluIDE6NCkgewogIGZmWzI6M10gPC0gbGFwcGx5KHBhc3RlMChjKCJ5IiwieCIpLCBpKSwgYXMubmFtZSkKICBwbG90KGZmLCBkYXRhID0gYW5zY29tYmUsIGNvbCA9ICJyZWQiLCBwY2ggPSAyMSwgYmcgPSAib3JhbmdlIiwgY2V4ID0gMS4yLAogICAgICAgeGxpbSA9IGMoMywgMTkpLCB5bGltID0gYygzLCAxMykpCiAgYWJsaW5lKG1vZHNbW2ldXSwgY29sID0gImJsdWUiKQogIGNjIDwtIGNvcihhbnNjb21iZVtbcGFzdGUwKGMoIngiLGkpLCBjb2xsYXBzZT0iIildXSwgYW5zY29tYmVbW3Bhc3RlMChjKCJ5IixpKSwgY29sbGFwc2U9IiIpXV0pCiAgdGl0bGUocGFzdGUwKCJjb3I9Iiwgcm91bmQoY2MsNCksIGNvbGxhcHNlPSIiKSkKfQptdGV4dCgiQW5zY29tYmUncyA0IFJlZ3Jlc3Npb24gZGF0YSBzZXRzIiwgb3V0ZXIgPSBUUlVFLCBjZXggPSAxLjUpCnBhcihvcCkKCmBgYAoKQWxvbmcgdGhlIHNhbWUgbGluZXMgaXMgdGhlIERhdGFzYXVydXMgZGF0YXNldCBvZiBBbGJlcnRvIENhaXJvLCB3aG8gY29uc3RydWN0cyAxMiBwYWlyd2lzZSBwbG90cwpzaGFyaW5nIHN1bW1hcnkgc3RhdHMgd2l0aCBhIHBvaW50IHBsb3Qgb2YgYSBkaW5vc2F1ci4gU2VlCjxodHRwczovL3d3dy5hdXRvZGVzay5jb20vcmVzZWFyY2gvcHVibGljYXRpb25zL3NhbWUtc3RhdHMtZGlmZmVyZW50LWdyYXBocz4KYW5kICA8aHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2RhdGFzYXVSdXMvdmlnbmV0dGVzL0RhdGFzYXVydXMuaHRtbD4KCgoKCgojIyBUaGUgZGF0YQoKV2Ugd2lsbCB1c2UgYSBiaWtlIHNoYXJpbmcgZGF0YXNldCwgZnJvbSB0aGUgVUNJIG1hY2hpbmUgbGVhcm5pbmcgYXJjaGl2ZSA8aHR0cHM6Ly9hcmNoaXZlLmljcy51Y2kuZWR1L21sL21hY2hpbmUtbGVhcm5pbmctZGF0YWJhc2VzLzAwMjc1Lz4KClRoZXJlIGlzIGRhaWx5IGRhdGEgYW5kIGhvdXJseSwgd2Ugd2lsbCB1c2UgdGhlIGhvdXJseS4KCgpUaGUgZGF0YSBpcyBhdmFpbGFibGUgaW4gdGhlIFIgcGFja2FnZSB0aW1ldGsuCgpgYGB7ciwgZ2V0ZGF0YX0KCmdsaW1wc2UoYmlrZV9zaGFyaW5nX2RhaWx5KQpgYGAKClRoaXMgZGF0YXNldCBjb250YWlucyB0aGUgZGFpbHkgY291bnQgb2YgcmVudGFsIGJpa2UgdHJhbnNhY3Rpb25zIGJldHdlZW4geWVhcnMgMjAxMSBhbmQgMjAxMiBpbiBDYXBpdGFsIGJpa2VzaGFyZSBzeXN0ZW0gd2l0aCB0aGUgY29ycmVzcG9uZGluZyB3ZWF0aGVyIGFuZCBzZWFzb25hbCBpbmZvcm1hdGlvbi4KClRoZSB0d28gdGVtcGVyYXR1cmUgdmFyaWFibGVzIHRlbXAgYW5kIGF0ZW1wIChmZWVsaW5nIHRlbXBlcmF0dXJlKSwgYm90aCBhcmUgbm9ybWFsaXNlZC4KCgpTb21lIG9mIHRoZXNlIHZhcmlhYmxlcyBzaG91bGQgYmUgZmFjdG9ycywgc28gd2Ugd2lsbCBmaXggdGhpcyB1cCBmaXJzdC4KCmBgYHtyLCBmaXhkYXRhfQpiaWtlIDwtIGJpa2Vfc2hhcmluZ19kYWlseQpiaWtlJHNlYXNvbiA8LSBjKCJXaW50ZXIiLCJTcHJpbmciLCAiU3VtbWVyIiwiRmFsbCIpW2Jpa2Ukc2Vhc29uXQpiaWtlJHNlYXNvbiA8LSBmYWN0b3IoYmlrZSRzZWFzb24sIGxldmVscyA9IGMoIldpbnRlciIsIlNwcmluZyIsICJTdW1tZXIiLCJGYWxsIikpICAKCmJpa2UkbW50aCA8LSBmYWN0b3IoYmlrZSRtbnRoKQoKYmlrZSRob2xpZGF5IDwtIGZhY3RvcihjKCJObyIsIlllcyIpW2Jpa2UkaG9saWRheSsxXSkKYmlrZSR3b3JraW5nZGF5IDwtIGZhY3RvcihjKCJObyIsIlllcyIpW2Jpa2Ukd29ya2luZ2RheSsxXSkKCmJpa2UkeXIgPC0gZmFjdG9yKGMoIjIwMTEiLCIyMDEyIilbYmlrZSR5cisxXSkKCmJpa2Ukd2VhdGhlcnNpdCA8LSBjKCJjbGVhciIsICJjbG91ZHkiLCAibGlnaHRQIiwgImhlYXZ5UCIpW2Jpa2Ukd2VhdGhlcnNpdF0KYmlrZSR3ZWF0aGVyc2l0IDwtIGZhY3RvcihiaWtlJHdlYXRoZXJzaXQsIGxldmVscz0gYygiY2xlYXIiLCAiY2xvdWR5IiwgImxpZ2h0UCIsICJoZWF2eVAiKSkKCmdsaW1wc2UoYmlrZSkKYGBgCgpBIGdvb2Qgd2F5IHRvIGNoZWNrIHRoZSBkYXRhIGZvciBvZGRpdGllcyBpcyB0byB1c2UgYSBwYWNrYWdlIHRoYXQgd2lsbCBkbyBhIHBsb3QsIGFuZCBzdW1tYXJ5IG9mIAphbGwgdGhlIHZhcmlhYmxlcy4gWW91IGNvdWxkIGRvIHRoaXMgbWFudWFsbHksIG9uZSB2YXJpYWJsZSBhdCBhIHRpbWUsIGJ1dCB0aGlzIHNhdmVzIHdvcms6CgpgYGB7ciwgcmVwb3J0LCBldmFsPUZ9CmRhdGFSZXBvcnRlcjo6bWFrZURhdGFSZXBvcnQoYmlrZSxyZXBsYWNlPVRSVUUpCmBgYAoKWW91IHdpbGwgZmluZCB0aGUgcmVzdWx0cyBhdCBkYXRhUmVwb3J0ZXJfYmlrZS5wZGYuCgpPbmUgdGhpbmcgdG8gbm90ZSBmcm9tIHRoZSByZXN1bHRzLCBpcyB0aGF0IGB3ZWF0aGVyc2l0JyBoYXMgYSB6ZXJvIGNvdW50IGZvciB0aGUgZm91cnRoIGxldmVsLgpXZSB3aWxsIGRyb3AgdGhpcy4KCmBgYHtyfQp0YWJsZShiaWtlJHdlYXRoZXJzaXQpCmJpa2UgPC0gZHJvcGxldmVscyhiaWtlKQp0YWJsZShiaWtlJHdlYXRoZXJzaXQpCmBgYAoKIyMgZ2dwbG90MgoKSW4gUiwgeW91IGNhbiB1c2UgYmFzZSBSIChiYXJwbG90LCBoaXN0LCBwbG90KS4gSGVyZSB3ZSB3aWxsIHVzZSBnZ3Bsb3QyIGZ1bmN0aW9ucy4KCmdncGxvdDIgaXMgYW4gUiBwYWNrYWdlIGF1dGhvcmVkIGJ5IEhhZGxleSBXaWNraGFtIGZvciBlbGVnYW50IGdyYXBoaWNzIGluIFIuCkNsaWNrIG9uIHRoaXMgbGluayBcdXJse2h0dHBzOi8vZ2dwbG90Mi50aWR5dmVyc2Uub3JnfSBmb3IgbG90cyBvZiBpbmZvcm1hdGlvbi4KCgpBbHNvIGNoZWNrIG91dCBoZSBwcmltZXIgYXQKXHVybHtodHRwczovL3JzdHVkaW8uY2xvdWQvbGVhcm4vcHJpbWVycy8zfQoKWW91IHdpbGwgZmluZCBtb3JlIHJlZmVyZW5jZXMgYXQgdGhlIGVuZCBvZiB0aGlzIGRvY3VtZW50LgoKCiMjIEJhc2ljIHBsb3RzCgojIyMgSGlzdG9ncmFtIHstfQpPdXIgcmVwb3J0IHVzZWQgb25lIHBsb3QgcGVyIHZhcmlhYmxlLCBiYXJwbG90cyBmb3IgZmFjdG9ycyBhbmQgaGlzdG9ncmFtcyBmb3IgbnVtZXJpYyB2YXJpYWJsZXMuCgoKCgpgYGB7ciwgZmlnLndpZHRoPTMuNSwgZmlnLmhlaWdodD0zLCBmaWcuYWxpZ249ImNlbnRlciJ9CmdncGxvdChkYXRhPWJpa2UsIAogICAgICAgbWFwcGluZz0gYWVzKHg9Y250KSkgKyBnZW9tX2hpc3RvZ3JhbSgpCmdncGxvdChkYXRhPWJpa2UsIG1hcHBpbmc9IGFlcyh4PWNudCkpICsgCiAgZ2VvbV9oaXN0b2dyYW0oZmlsbD0ibGlnaHRibHVlIiwgY29sb3I9Im5hdnkiKQpgYGAKCi0gSGVyZSBtYXBwaW5nIGRlc2NyaWJlcyBob3cgdGhlIGRhdGEgaXMgbWFwcGVkIHRvIGFlc3RoZXRpY3MuCgotIEFlc3RoZXRpY3MgYXJlIHdoYXQgeW91IHNlZSBvbiB0aGUgcGxvdC4gSGVyZSB0aGF0IGBjbnRgIGdvZXMgb24gdGhlIHggYXhpcy4KCi0gVGhlIGdlb20gaXMgd2hhdCBpcyBkcmF3biwgaGVyZSBhIGhpc3RvZ3JhbS4KCi0gSW4gdGhlIHNlY29uZCB2YXJpYW50LCB0aGUgZmlsbCBjb2xvdXIgYW5kIG91dGxpbmUgY29sb3VyIGZvciB0aGUgYmFycyBpcyBzcGVjaWZpZWQuCgotIFlvdSBjb3VsZCB1c2UgYW5vdGhlciB2YXJpYWJsZSBmb3IgdGhlIGZpbGwgb3IgY29sb3I6CgpgYGB7ciwgZmlnLndpZHRoPTQuNSwgZmlnLmhlaWdodD0zLjUsIGZpZy5hbGlnbj0iY2VudGVyIn0KZ2dwbG90KGRhdGE9YmlrZSwgbWFwcGluZz0gYWVzKHg9Y250LCBmaWxsPXlyKSkgKyAKICBnZW9tX2hpc3RvZ3JhbShjb2xvcj0iYmxhY2siKQpgYGAKCi0gWW91IGFyZSBzZWVpbmcgaGVyZSBmb3IgZWFjaCBiYXIsIHRoZSBjb3VudCBmb3IgMjAxMSBhbmQgMjAxMi4gCgojIyMgQmFycGxvdCB7LX0KCkZvciB0aGUgYmFycGxvdCwgdGhlIHggdmFyaWFibGUgaXMgYSBmYWN0b3IsIG9yIHNvbWV0aGluZyB0aGF0IGNhbiBiZSBpbnRlcnByZXRlZCBhcyBhIGZhY3Rvci4KCmBgYHtyLCAgZmlnLndpZHRoPTMuNSwgZmlnLmhlaWdodD0zLCBmaWcuYWxpZ249ImNlbnRlciJ9CmdncGxvdChkYXRhPWJpa2UsIG1hcHBpbmc9IGFlcyh4PXdlYXRoZXJzaXQpKSArIAogIGdlb21fYmFyKGZpbGw9ImxpZ2h0Ymx1ZSIsIGNvbG9yPSJuYXZ5IikKYGBgCgpZb3UgY2FuIGFkZCBjb2xvciB0byB0aGlzCgpgYGB7ciwgIGZpZy53aWR0aD00LjUsIGZpZy5oZWlnaHQ9MywgZmlnLmFsaWduPSJjZW50ZXIifQpnZ3Bsb3QoZGF0YT1iaWtlLCBtYXBwaW5nPSBhZXMoeD13ZWF0aGVyc2l0LCBmaWxsPXlyKSkgKyAKICBnZW9tX2JhciggKQpgYGAKCmFuZCBzd2l0Y2ggdG8gcHJvcG9ydGlvbnMgd2l0aAoKYGBge3IsICBmaWcud2lkdGg9NC41LCBmaWcuaGVpZ2h0PTMsIGZpZy5hbGlnbj0iY2VudGVyIn0KZ2dwbG90KGRhdGE9YmlrZSwgbWFwcGluZz0gYWVzKHg9d2VhdGhlcnNpdCwgZmlsbD15cikpICsgCiAgZ2VvbV9iYXIoIHBvc2l0aW9uPSJmaWxsIikKYGBgCgpgZ2VvbV9jb2xgIGlzIGxpa2UgYGdlb21fYmFyYCwgZXhjZXB0IHlvdSBoYXZlIGEgeSB2YXJpYWJsZS4KVGhlIHBsb3QgYmVsb3cgc2hvd3MgdGhlIG51bWJlciBvZiBiaWtlcyByZW50ZWQgZm9yIGVhY2ggbW9udGgsIGFuZCBpbiB0aGUgc2Vjb25kCnZhcmlhbnQgd2UgY29sb3VyIGJ5IHllYXIuCgpgYGB7ciwgIGZpZy53aWR0aD00LjUsIGZpZy5oZWlnaHQ9MywgZmlnLmFsaWduPSJjZW50ZXIifQoKZ2dwbG90KGRhdGE9YmlrZSwgbWFwcGluZz0gYWVzKHg9bW50aCwgeT1jbnQpKSArIAogIGdlb21fY29sKGNvbG9yPSJsaWdodGJsdWUiLCBmaWxsPSJsaWdodGJsdWUiKQoKCmdncGxvdChkYXRhPWJpa2UsIG1hcHBpbmc9IGFlcyh4PW1udGgsIHk9Y250LCBmaWxsPXlyKSkgKyAKICBnZW9tX2NvbCgpCgpgYGAKCgojIyMgQSBzY2F0dGVycGxvdCB7LX0KClRoZSBzY2F0dGVycGxvdCBpcyBmb3IgdHdvIG51bWVyaWMgdmFyaWFibGVzLCBhbmQgeW91IHVzZSBgZ2VvbV9wb2ludGAKCmBgYHtyLCAgZmlnLndpZHRoPTMuNSwgZmlnLmhlaWdodD0zLCBmaWcuYWxpZ249ImNlbnRlciJ9CmdncGxvdChkYXRhPWJpa2UsIGFlcyh4PXRlbXAsIHk9Y250KSkgKyBnZW9tX3BvaW50KCkKYGBgCgpXZSBjYW4gZWFzaWx5IGJyaW5nIGEgdGhpcmQgIHZhcmlhYmxlIGZvciB0aGUgcG9pbnQgY29sb3VyLCBhbmQgYSBmb3VydGggZm9yIHNoYXBlOgoKYGBge3IsICBmaWcud2lkdGg9NC41LCBmaWcuaGVpZ2h0PTMsIGZpZy5hbGlnbj0iY2VudGVyIn0KZ2dwbG90KGRhdGE9YmlrZSwgYWVzKHg9dGVtcCwgeT1jbnQsIGNvbG9yPXlyKSkgKyAKICBnZW9tX3BvaW50KCkKCmdncGxvdChkYXRhPWJpa2UsIGFlcyh4PXRlbXAsIHk9Y250LCBjb2xvcj15ciwgc2hhcGU9c2Vhc29uKSkgKyAKICBnZW9tX3BvaW50KCkKCgpgYGAKVGhlIHBsb3Qgd2l0aCBzaGFwZSBhbmQgY29sb3VyIHdvcmtzIGJldHRlciB3aGVuIHRoZXJlIGlzIG1vcmUgc2VwYXJhdGlvbiBiZXR3ZWVuIHRoZSBmcm91cHMuCgpJbiB0aGUgcGxvdCBiZWxvdywgd2Vla2RheSBpcyBhIG51bWVyaWMgdmFyaWFibGUgc28gaXQgaXMgbWFwcGVkIHRvIHNoYWRlcyBvZiBibHVlLgpUaGlzIHZhcmlhYmxlIGlzIGJldHRlciByZXByZXNlbnRlZCBhcyBhIGZhY3Rvciwgd2hpY2ggY2hhbmdlcyB0aGUgZ2dwbG90LgoKCmBgYHtyLCAgZmlnLndpZHRoPTQuNSwgZmlnLmhlaWdodD0zLCBmaWcuYWxpZ249ImNlbnRlciJ9CmdncGxvdChkYXRhPWJpa2UsIGFlcyh4PXRlbXAsIHk9Y250LCBjb2xvcj13ZWVrZGF5KSkgKyAKICBnZW9tX3BvaW50KCkKCmJpa2Ukd2Vla2RheSA8LSBmYWN0b3IoYmlrZSR3ZWVrZGF5KQoKZ2dwbG90KGRhdGE9YmlrZSwgYWVzKHg9dGVtcCwgeT1jbnQsIGNvbG9yPXdlZWtkYXkpKSArIAogIGdlb21fcG9pbnQoKQoKYGBgCldvdWxkIHdlIGV4cGVjdCB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gY250IGFuZCB0ZW1wIHRvIGNoYW5nZSB3aXRoIHdlZWtkYXk/CgoKCgojIyMgQSB0aW1lIHBsb3QKCmBgYHtyLCAgZmlnLndpZHRoPTUuNSwgZmlnLmhlaWdodD0zLCBmaWcuYWxpZ249ImNlbnRlciJ9CmdncGxvdChkYXRhPWJpa2UsIGFlcyh4PWR0ZWRheSwgeT1jbnQpKSArIAogIGdlb21fbGluZSgpCgpgYGAKV2UgY291bGQgdXNlIGBnZW9tX3BvaW50YCBoZXJlIGJ1dCBgZ2VvbV9saW5lYCBjb25uZWN0cyB0aGUgZG90cy4KCk5vdGUgdGhhdCBgZHRlZGF5YCBpcyBhIGBkYXRlYCB2YXJpYWJsZSBpbiB0aGUgZGF0YXNldCwgd2hpY2ggaXMgaW50ZXJwcmV0ZWQgY29ycmVjdGx5IGZvciB0aGUgcGxvdC4KCmBgYHtyLCAgZmlnLndpZHRoPTUuNSwgZmlnLmhlaWdodD0zLCBmaWcuYWxpZ249ImNlbnRlciJ9CmxpYnJhcnkobHVicmlkYXRlKQpnZ3Bsb3QoZGF0YT1iaWtlLCBhZXMoeD15ZGF5KGR0ZWRheSksIHk9Y250LCBjb2xvcj15cikpICsgCiAgZ2VvbV9saW5lKCkKYGBgCgojIyBQcmFjdGljZSAxCgpVc2luZyB0aGUgYHBlbmd1aW5zYCBkYXRhc2V0LCBtYWtlIHRoZXNlIHR3byBwbG90cwpgYGB7ciwgZmlnLndpZHRoPTQuNSwgZmlnLmhlaWdodD0zLCBmaWcuYWxpZ249ImNlbnRlciIsIGVjaG89Rn0KCmdncGxvdChwZW5ndWlucywgYWVzKHggPSBpc2xhbmQsIGZpbGwgPSBzcGVjaWVzKSkgKwogIGdlb21fYmFyKCkgCgoKZ2dwbG90KGRhdGEgPSBwZW5ndWlucywgYWVzKHggPSBiaWxsX2xlbmd0aF9tbSwgeSA9IGJpbGxfZGVwdGhfbW0pKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBzcGVjaWVzLAogICAgICAgICAgICAgICAgIHNoYXBlID0gc3BlY2llcykpIApgYGAKCiMjIEFuYXRvbXkgb2YgYSBnZ3Bsb3QKCldlIGhhdmUgc2VlbiB0aGF0ICoqYWVzdGhldGljcyoqIGFyZSBsaW5rZWQgdG8gdmFyaWFibGVzIGluIGEgZGF0YXNldC4gQWVzdGhldGljcwp3ZSBoYXZlIGNvbnNpZGVyZWQgYXJlIGB4YCwgYHlgLCBgZmlsbGAgYW5kIGBjb2xvcmAuCgpIZXJlIGFyZSBzb21lIG90aGVyIGFlc3RoZXRpY3MgKGZpZ3VyZSBmcm9tIFdpbGtlIGJvb2spCgpgYGB7ciwgZWNobz1GLCBldmFsPVQsIG91dC53aWR0aD0iNzUlIiwgZmlnLmFsaWduPSdjZW50ZXInfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiZmlncy9hZXMucG5nIikKYGBgCgotIHNoYXBlIGFuZCBsaW5lIHR5cGUgYWVzdGhldGljcyBhcmUgZm9yIGNhdGVnb3JpY2FsIGRhdGEgKGllIGZhY3RvcnMpCi0gb3RoZXJzIGNhbiByZXByZXNlbnQgZmFjdG9ycyBvciBudW1lcmljIHZhcmlhYmxlcwoKKipzY2FsZXMqKiBtYXAgZGF0YSB2YWx1ZXMgaW50byBhZXN0aGV0aWNzLiBFZyB1c2UgYHNjYWxlX3hfbG9nMTAoKWAgdG8gbG9nIHRoZSB4IGNvb3JkaW5hdGVzLgpEZWZhdWx0IGlzIGBzY2FsZV94X2NvbnRpbnVvdXNgLiBBZGp1c3QgdGhpcyB0byBjaGFuZ2UgYXhlcywgZm9yIGV4YW1wbGUKCgpgYGB7ciwgIGZpZy53aWR0aD02LjUsIGZpZy5oZWlnaHQ9MywgZmlnLmFsaWduPSJjZW50ZXIifQoKZ2dwbG90KGRhdGE9YmlrZSwgYWVzKHg9eWRheShkdGVkYXkpLCB5PWNudCwgY29sb3I9eXIpKSArIAogIGdlb21fbGluZSgpKwogIHNjYWxlX3hfY29udGludW91cyhicmVha3M9Y3Vtc3VtKGMoMSwzMSwyOCwzMSwzMCwzMSwzMCwzMSwzMSwzMCwzMSwzMCkpLAogICAgICAgICAgICAgICAgICAgICBsYWJlbHM9bW9udGguYWJiWzE6MTJdKSsKICB4bGFiKCJEYXRlIikKYGBgCgoqKmxheWVycyoqICBjb21wb25lbnRzIG9mIHRoZSBwbG90IGFyZSBhZGRlZCBpbiBsYXllcnMsIHVzaW5nIGArYAoKKipsZWdlbmQqKiByZWZlcnMgdG8gdGhlIGNvbG9yIGluZGV4IG9uIHRoZSByaWdodC4gWW91IHdpbGwgZ2V0IGxlZ2VuZHMgZm9yIGZpbGwsIGxpbmV0eXBlIGFuZCBvdGhlciBhZXN0aGV0aWNzLgoKKip0aGVtZXMqKiByZWZlciB0byBub24tZGF0YSBjb21wb25lbnRzLCB0aGUgYmFja2dyb3VuZCwgdGl0bGUsIGZvbnQgYW5kIHNvIG9uLiBXb3JrIG9uIHRoaXMgdG8gcHJldHR5LXVwIHRoZSBwbG90LgoKKipmYWNldHMqKiBhcmUgaW1wb3J0YW50LCB0aGlzIGlzIGEgd2F5IHRvIHNwbGl0IHVwIGEgcGxvdCBieSBhIGZhY3Rvci4gSXQgaW5jcmVhc2VzIHRoZSBudW1iZXIgb2YgZGltZW5zaW9ucyB0aGF0IGNhbgpiZSBwbG90dGVkLgoKYGBge3IsICBmaWcud2lkdGg9Ni41LCBmaWcuaGVpZ2h0PTUuNSwgZmlnLmFsaWduPSJjZW50ZXIifQoKZ2dwbG90KGRhdGE9YmlrZSwgYWVzKHg9dGVtcCwgeT1jbnQsIGNvbG9yPXlyKSkgKyAKICBnZW9tX3BvaW50KCkrCiAgZmFjZXRfd3JhcCh+IHNlYXNvbikKYGBgCgoKIyMgTW9yZSBvbiBsYXllcnMKCkluIGdncGxvdCB0aGUgaWRlYSBpcyB0aGF0IHlvdSBidWlsZCB1cCB0byB0aGUgZGVzaXJlZCBwbG90IGJpdCBieSBiaXQuCgpHb2luZyBiYWNrIHRvIHRoZSBmaXJzdCBzY2F0dGVycGxvdCwgd2UgY2FuIGFkZCBhIHNtb290aCwgYW5kIG9yIGEgcmVncmVzc2lvbiBsaW5lOgoKYGBge3IsIGZpZy53aWR0aD0zLjUsIGZpZy5oZWlnaHQ9MywgZmlnLmFsaWduPSJjZW50ZXIifQpnZ3Bsb3QoZGF0YT1iaWtlLCBhZXMoeD10ZW1wLCB5PWNudCkpICsgCiAgZ2VvbV9wb2ludCgpKwogIGdlb21fc21vb3RoKCkKCnAgPC0gZ2dwbG90KGRhdGE9YmlrZSwgYWVzKHg9dGVtcCwgeT1jbnQpKSArIAogICAgIGdlb21fcG9pbnQoKQoKcCArIGdlb21fc21vb3RoKG1ldGhvZD0ibG0iLCBzZT1GQUxTRSkKYGBgCgpCeSBkZWZhdWx0IGBnZW9tX3Ntb290aGAgdXNlcyBsb2VzcywgZm9yIDEwMDAgb2JzZXJ2YXRpb25zIG9yIGxlc3MsIGFuZCBhIGdhbSBvdGhlcndpc2UuCgpUaGUgaW50ZXJlc3RpbmcgdGhpbmcgaXMgaWYgeW91IGhhdmUgY29sb3JzIG9yIGdyb3VwcywgdGhlIGBnZW9tX3Ntb290aGAgd2lsbCByZXNwZWN0IHRoYXQ6CgpgYGB7ciwgZmlnLndpZHRoPTQuNSwgZmlnLmhlaWdodD0zLCBmaWcuYWxpZ249ImNlbnRlciJ9CmdncGxvdChkYXRhPWJpa2UsIGFlcyh4PXRlbXAsIHk9Y250LCBjb2xvcj15cikpICsgCiAgZ2VvbV9wb2ludCgpKwogIGdlb21fc21vb3RoKHNlPUZBTFNFKQoKYGBgCgojIyBNb3JlIG9uIGZhY2V0cwoKTW9yZSB0aGFuIG9uZSB2YXJpYWJsZSBjYW4gYmUgYWRkZWQgdHAgdGhlIGZhY2V0IHdyYXAuIEZvciBleGFtcGxlLCB5b3UgY291bGQgdXNlCmBmYWNldF93cmFwKH4gc2Vhc29uKyB5cilgLApvciBldmVuIGJldHRlciBgZmFjZXRfZ3JpZGAKCmBgYHtyLCAgZmlnLndpZHRoPTksIGZpZy5oZWlnaHQ9NCwgZmlnLmFsaWduPSJjZW50ZXIifQoKZ2dwbG90KGRhdGE9YmlrZSwgYWVzKHg9dGVtcCwgeT1jbnQsIGNvbG9yPXlyKSkgKyAKICBnZW9tX3BvaW50KCkrCiAgZmFjZXRfZ3JpZCggd29ya2luZ2RheSB+IHNlYXNvbikKYGBgCgpBbmQgaWYgd2UgYWRkIHNtb290aHMgb3Igb3RoZXIgZml0cywgdGhlc2Ugd2lsbCByZXNwZWN0IHRoZSBncm91cHMgZm9yIHRoZSBjb2xvcnMgYW5kIGZhY2V0cy4KCgpgYGB7ciwgZmlnLndpZHRoPTksIGZpZy5oZWlnaHQ9NCwgZmlnLmFsaWduPSJjZW50ZXIifQpnZ3Bsb3QoZGF0YT1iaWtlLCBhZXMoeD10ZW1wLCB5PWNudCwgY29sb3I9eXIpKSArIAogIGdlb21fcG9pbnQoKSsKICBmYWNldF9ncmlkKCB3b3JraW5nZGF5IH4gc2Vhc29uKSsKICBnZW9tX3Ntb290aChzZT1GKQpgYGAKClN1cHBvc2UgeW91IHdhbnQgdG8gZml0IHJlZ3Jlc3Npb24gbGluZXMgaW5zdGVhZCwgdGhlbiBzaW1wbHkgdXNlIGBtZXRob2Q9bG1gIGluIHRoZSBgZ2VvbV9zbW9vdGhgLgoKVGhpcyB3aWxsIGJlIGVzc2VudGlhbGx5IGZpdHRpbmcgdGhlIG1vZGVsIHRvIHRoZSBpbnRlcmFjdGlvbiBvZiBgc2Vhc29uYCBhbmQgYHlyYCB3aXRoIGB0ZW1wYC4KClRvIGNvbXBhcmUgdGhlIGZpdCB3aXRoIGFuZCB3aXRob3V0IGludGVyYWN0aW9uLCB3ZSBjcmVhdGUgdGhlIGZpdCBhbmQKYWRkZWQgdGhlIGZpdHRlZCB2YWx1ZXMgdG8gdGhlIGRhdGFzZXQsIGFuZCB0aGVuIHVzZSBgZ2VvbV9saW5lYC4KCmBgYHtyLCAgZmlnLndpZHRoPTksIGZpZy5oZWlnaHQ9NCwgZmlnLmFsaWduPSJjZW50ZXIifQpmaXQgPC0gbG0oY250IH4gdGVtcCtzZWFzb24reXIsIGRhdGE9YmlrZSkKYmlrZSAlPiUgCiAgbXV0YXRlKGZpdCA9IGZpdHRlZChmaXQpKSAlPiUKICBnZ3Bsb3QoYWVzKHg9dGVtcCwgeT1jbnQsIGNvbG9yPXlyKSkgKyAKICBnZW9tX3BvaW50KHNpemU9MSkrICAjIHBvaW50cyBzbWFsbGVyCiAgZmFjZXRfZ3JpZCggd29ya2luZ2RheSB+IHNlYXNvbikrCiAgZ2VvbV9zbW9vdGgobWV0aG9kPWxtLCBzZT1GKSsKICBnZW9tX2xpbmUoYWVzKHk9Zml0KSwgbGluZXR5cGU9InR3b2Rhc2giLCBzaXplPTEuNSkKYGBgCgpUaGUgZml0IHdpdGhvdXQgaW50ZXJhY3Rpb24gZG9lcyBwb29ybHkgZm9yIHN1bW1lci4KCgoKCgojIyBQcmFjdGljZSAyCgpVc2luZyB0aGUgcGVuZ3VpbnMgZGF0YXNldCwgbWFrZSB0aGVzZSB0d28gcGxvdHMKCgpgYGB7ciwgZmlnLndpZHRoPTkuNSwgZmlnLmhlaWdodD0zLCBmaWcuYWxpZ249ImNlbnRlciIsIGVjaG89Rn0KCmdncGxvdChkYXRhID0gcGVuZ3VpbnMsIGFlcyh4ID0gYmlsbF9sZW5ndGhfbW0sIHkgPSBiaWxsX2RlcHRoX21tKSkgKwogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gc2V4KSkrCiAgZmFjZXRfd3JhcCh+IHNwZWNpZXMpCgoKZ2dwbG90KGRhdGEgPSBwZW5ndWlucywgYWVzKHggPSBiaWxsX2xlbmd0aF9tbSwgeSA9IGJpbGxfZGVwdGhfbW0sIGNvbG9yPXNleCkpICsKICBnZW9tX3BvaW50KCkrCiAgZmFjZXRfd3JhcCh+IHNwZWNpZXMpKwogIGdlb21fc21vb3RoKG1ldGhvZD0ibG0iLCBzZT1GQUxTRSkrCiAgc2NhbGVfY29sb3VyX2Rpc2NyZXRlKG5hLnRyYW5zbGF0ZSA9IEZBTFNFKQpgYGAKCkhpbnQ6IHRoZXJlIGFyZSBhIGZldyB3YXlzIHRvIGdldCByaWQgb2YgdGhlIE5BIGluIHRoZSBgc2V4YCBsZWdlbmQsIG9uZSBpcyB0byB1c2UKYHNjYWxlX2NvbG91cl9kaXNjcmV0ZShuYS50cmFuc2xhdGUgPSBGQUxTRSlgCgpBZnRlciB0aGF0IHlvdSBjYW4gY2hlY2sgb3V0IG1hbnkgbW9yZSBleGFtcGxlcyB3aXRoIHRoaXMgZGF0YSBhdAo8aHR0cHM6Ly9hbGxpc29uaG9yc3QuZ2l0aHViLmlvL3BhbG1lcnBlbmd1aW5zL2FydGljbGVzL2ludHJvLmh0bWw+CgoKIyMgQWx0ZXJuYXRpdmVzIHRvIGhpc3RvZ3JhbQoKIyMjIERlbnNpdHkgcGxvdCB7LX0KCldlIGNhbiBmaXQgYSBkZW5zaXR5IGVzdGltYXRlIHRvIG9uZSBudW1lcmljIHZhcmlhYmxlLCBqdXN0IHJlcGxhY2UgYGdlb21faGlzdG9ncmFtYAp3aXRoIGBnZW9tX2RlbnNpdHlgCgpIZXJlIHdlIGNvbXBhcmUgdGhlIGhpc3RvZ3JhbSBhbmQgZGVuc2l0eQoKYGBge3IsIGZpZy53aWR0aD0zLjUsIGZpZy5oZWlnaHQ9MywgZmlnLmFsaWduPSJjZW50ZXIiLCBldmFsPUZ9CmdncGxvdChkYXRhPWJpa2UsIGFlcyh4PWNudCkpICsgCiAgZ2VvbV9kZW5zaXR5KGNvbG9yPSJyZWQiKSsKICBnZW9tX2hpc3RvZ3JhbShhZXMoeT1hZnRlcl9zdGF0KGRlbnNpdHkpKSxmaWxsPSJsaWdodGJsdWUiLCBjb2xvcj0ibmF2eSIsIGFscGhhPS41KQpgYGAKCklmIHlvdSB1c2UgYSBmaWxsIGFlc3RoZXRpYyBmb3IgdGhlIGRlbnNpdHkgeW91IGdldApgYGB7ciwgZmlnLndpZHRoPTQuNSwgZmlnLmhlaWdodD0zLCBmaWcuYWxpZ249ImNlbnRlciIsIGV2YWw9Rn0KZ2dwbG90KGRhdGE9YmlrZSwgYWVzKHg9Y250LCBmaWxsPXlyKSkgKyAKICBnZW9tX2RlbnNpdHkoYWxwaGE9LjUpCmBgYApgYWxwaGFgIGdpdmVzIHNvbWUgdHJhbnNwYXJlbmN5LgogCiMjIyBCb3hwbG90cyB7LX0KQSBib3hwbG90IGluIGdncGxvdCBuZWVkcyBhIHkgYW5kIHggdmFyaWFibGUuIAoKYGBge3IsIGZpZy53aWR0aD00LjUsIGZpZy5oZWlnaHQ9MywgZmlnLmFsaWduPSJjZW50ZXIifQpnZ3Bsb3QoZGF0YT1iaWtlLCBhZXMoeT1jbnQseD1zZWFzb24pKSArIAogIGdlb21fYm94cGxvdCh2YXJ3aWR0aD1UUlVFKSAjIHdpZHRoIG9mIGJveCBwcm9wb3J0aW9uYWwgdG8gc3FydCBvZiBub2JzCmBgYAoKSWYgeW91IGRvIG5vdCBuZWVkIGFuIHggdmFyaWFibGUgdXNlOgoKYGBge3IsIGZpZy53aWR0aD0zLjUsIGZpZy5oZWlnaHQ9MywgZmlnLmFsaWduPSJjZW50ZXIiLCBldmFsPUZ9CmdncGxvdChkYXRhPWJpa2UsIGFlcyh5PWNudCksIHg9MCkgKyAjIGFueSBudW1iZXIgd2lsbCBkbwogIGdlb21fYm94cGxvdCgpICAKYGBgCgoKVmlvbGluIHBsb3RzIGFyZSBhIHZhcmlhbnQgdXNpbmcgYSBkZW5zaXR5IGZvbGRlZC1vdmVyCgpgYGB7ciwgZmlnLndpZHRoPTQuNSwgZmlnLmhlaWdodD0zLCBmaWcuYWxpZ249ImNlbnRlciJ9CmdncGxvdChkYXRhPWJpa2UsIGFlcyh5PWNudCx4PXNlYXNvbikpICsgCiAgZ2VvbV92aW9saW4oZmlsbD0ibGlnaHRibHVlIikKYGBgCgoKIyMgUmVmZXJlbmNlcwoKRGF0YSB2aXN1YWxpemF0aW9uIGEgcHJhY3RpY2FsIGludHJvZHVjdGlvbiBieSBLaWVyYW4gSGVhbHkgPGh0dHBzOi8vc29jdml6LmNvPgoKPCEtLSBodHRwczovL3d3dy5jZWRyaWNzY2hlcmVyLmNvbS8yMDE5LzA4LzA1L2EtZ2dwbG90Mi10dXRvcmlhbC1mb3ItYmVhdXRpZnVsLXBsb3R0aW5nLWluLXIvIC0tPgoKRnVuZGFtZW50YWxzIG9mIERhdGEgVmlzdWFsaXphdGlvbiBieSBDbGF1cyBXaWxrZSA8aHR0cHM6Ly9jbGF1c3dpbGtlLmNvbS9kYXRhdml6Lz4gKG5vIFIpIAoKCmBgYHtyLCBlY2hvPUZBTFNFLCBldmFsPUZBTFNFLCBwdXJsPUZBTFNFfQprbml0cjo6cHVybCgibGVjdDEuUm1kIikKYGBgCgo=